// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//

#import "WWTCLKit.h"

#import "EveCommand.h"
#import "RIBCommand.h"
#import "RIBTranslate.h"
#import "WWEveParser.h"
#import "WW3DShape.h"
#import "WW3DWell.h"
#import "Protocol_WWAnimatable.h"
#import "WW3DAttributeState.h"
#import "usefulWW3DFunctions.h"

#import "WWSceneClock.h"  // has the sceneClock protocol - formalize this!

#import <string.h>

@interface WW3DShape(Private)
- updateEveCmd:(EveCommand *)eveCmd;
@end

// this block was put in for Ian - wave has no idea what it does...
@implementation WW3DShape(Private)
static const char *RIBTranslateCmd = "Translate";
static const char *RIBTranslateLimitation = "Currently require all the Translate arguments appearing in an EveCmd to be traced variables.";

- updateEveCmd:(EveCommand *)eveCmd
{
    WWTCLClosedCmd *tclClosedCmd;
    WWTCLVarTrace *trace;
    char transComp[100];
    List *tracedVars;
    const char *cmd;
    int nVars;
    
    tclClosedCmd = [eveCmd command];
    cmd = [tclClosedCmd cmd];
    if (strstr(cmd, RIBTranslateCmd)) {
	tracedVars = [tclClosedCmd tracedVars];
	nVars = [tracedVars count];
	if (nVars < 3) {
	    NXLogError(RIBTranslateLimitation);
	    return self;
	}
#if 0
	trace = [tracedVars objectAt:0];
	sprintf(transComp, "%f", transform[3][0]);
	[interp setVar:[trace varName] toValue:transComp];
	trace = [tracedVars objectAt:1];
	sprintf(transComp, "%f", transform[3][1]);
	[interp setVar:[trace varName] toValue:transComp];
	trace = [tracedVars objectAt:2];
	sprintf(transComp, "%f", transform[3][2]);
	[interp setVar:[trace varName] toValue:transComp];
#endif
	strcpy(transComp, "0.0");
	trace = [tracedVars objectAt:0];
	[interp setVar:(char *)[trace varName] toValue:transComp];
	trace = [tracedVars objectAt:1];
	[interp setVar:(char *)[trace varName] toValue:transComp];
	trace = [tracedVars objectAt:2];
	[interp setVar:(char *)[trace varName] toValue:transComp];
    }
    return self;
}
@end

@implementation WW3DShape


+ initialize {  [WW3DShape setVersion:2]; return self; }

- init
{
  [super init];

  ribCommandList = [[List alloc] init];
  childList = [[List alloc] init];
  siblingList = [[List alloc] init];

  unselectedColor[0] = 0.9;  unselectedColor[1] = 0.9;  unselectedColor[2] = 0.9;

  selectedColor[0] = 0.9;  selectedColor[1] = 0.9;  selectedColor[2] = 0.0;

  xColor[0] = 1.0;  xColor[1] = 0.0;  xColor[2] = 0.0;
  xExtent = 0.0;

  yColor[0] = 0.0;  yColor[1] = 1.0;  yColor[2] = 0.0;
  yExtent = 0.0;

  zColor[0] = 0.0;  zColor[1] = 0.0;  zColor[2] = 1.0;
  zExtent = 0.0;

  selected = NO;
  drawOrigin = NO;
  [self setSelectable:YES];

  shadingGroup = NULL;
  materialGroup = NULL;
  geometryGroup = NULL;

  pathSeparator = '/';

  dirtyBoundingBox = YES;
  dirtyBases = YES;

  interp = nil;
  sceneClock = nil;

  hasEveCmd = NO;
  
  originTesselationVector[0] = originTesselationVector[1] = 4.0;

  return self;
}

- initWithInterp:newInterp andSceneClock:newSceneClock
{
  [self init];
  interp = newInterp;
  sceneClock = newSceneClock;

  return self;
}

// need to put awake in here...

- awake
{
  [super awake];
  originTesselationVector[0] = originTesselationVector[1] = 4.0;
/*
  NXLogError("in awake, WW3DShape <%s> boundingBox is (%f, %f) (%f, %f) (%f, %f)\n",
             [self shapeName],
             boundingBox[0], boundingBox[1], boundingBox[2], boundingBox[3], boundingBox[4], boundingBox[4]);
*/ 
  return self;
}

- free
{
  //NXLogError("WW3DShape %s (%p) %p being free'ed\n", [self shapeName], [self shapeName], self);
  [[ribCommandList freeObjects] free];

  // we assume super will send the free message to our descendants...
  //NXLogError("childList %p being free'ed\n", childList);
  [childList free];
  //NXLogError("siblingList %p being free'ed\n", siblingList);
  [siblingList free];

  return [super free];
}

- copyFromZone:(NXZone *)zone  
{  
   id   newCopy = [super copyFromZone:zone];

   NXLogError("WARNING: copyFromZone: incompletely implemented for %s", [[self class] name]);

   return newCopy;
}

// this method was put in for Ian - wave has no idea what it does...
- performUpdateForInteraction:(WW3DCamera *)camera
/*
 * For now, we hit the simplicity trail...
 * We enumerate the ribCommandList; for each EveCommand
 * encountered, we determine if it conforms to the WWRenderable
 * protocol. If it does, and it affects the transformation
 * matrix of the shape, then we go ahead and write the various
 * (traced) variables for the RIBCommand.
 */
{
    if (hasEveCmd) {
	WWEveParser *eveParser;
	int count, i;
	
	eveParser = [[camera delegate] parser];
	count = [ribCommandList count];
	for (i = 0; i < count; i++) {
	    id aCmd = [ribCommandList objectAt:i];
	    if ([aCmd isKindOf:[EveCommand class]]) {
		[self updateEveCmd:aCmd];
	    }
	}
    }
    return self;
}

- tclInterp { return interp; }

- sceneClock { return sceneClock; }


- transformCTM:(WW3DAttributeState *)attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{  NXLogError("why are you calling transformCTM:startingAt:endingAt: for WW3DShape <%s>?\n", [self shapeName]);
   return self;
}

- (BOOL)isSelectable { return YES; } // This is a N3DKit thing, not mine

- (BOOL)isLerpable { return NO; }
- lerpWith:b by:(float)uValue { return self; }
- lerpSelfWith:b by:(float)uValue { return self; }

- (BOOL)isMotionBlurrable { return YES; }  // this isn't strictly true, ya know...

- (BOOL)isCompoundCommand { return YES; } // this isn't strictly true, ya know...

- (BOOL)hasBoundingBox { return YES; }  // this isn't strictly true, ya know...

- (float)lastSampleIsAt
{
   float  oldestSample = 0.0, newSample;
   int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  newSample = [(id <WWRenderable>)[ribCommandList objectAt:i] lastSampleIsAt];
     if (newSample > oldestSample)
     {  oldestSample = newSample;
     }
  }

  newSample = [descendant lastSampleIsAt];
  if (newSample > oldestSample)
  {  oldestSample = newSample;
  }

  newSample = [nextPeer lastSampleIsAt];
  if (newSample > oldestSample)
  {  oldestSample = newSample;
  }

  return oldestSample;
}

- (unsigned long int)maxSampleBandwidth;
{
   unsigned long int  maxSampleBandwidth = 50;  //WAVE FIX ME
   int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  maxSampleBandwidth += [(id <WWRenderable>)[ribCommandList objectAt:i] maxSampleBandwidth];
  }

  maxSampleBandwidth += [descendant maxSampleBandwidth];
  maxSampleBandwidth += [nextPeer maxSampleBandwidth];

  return maxSampleBandwidth;
}


- (BOOL)isMoot
{
  return NO;
}

- (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime  {  return [self isMoot];  }

- (BOOL)theSameAs:otherRIBCommand
{
  return NO;
}

- (BOOL)similarTo:otherRIBCommand 
{
  if ([self class] != [otherRIBCommand class])
  {  return NO;
  }
  return YES;
}


- setShader_:newShader { return [super setShader:newShader]; }

- removeSurfaceShader:sender
{
  if (surfaceShader)
  {  [surfaceShader free];
  }
  surfaceShader = nil;
  return self;
}

- removeDisplacementShader:sender
{
  if (displacementShader)
  {  [displacementShader free];
  }
  displacementShader = nil;
  return self;
}


- setBoundingBox:(RtBound *)newBoundingBox { return nil; }

// this is really a private method when some part of a shape (i.e. one
// of it's rib commands) does something that it knows probably
//invalidates the entire shape's bounding box.
- setBoundingBoxDirty { dirtyBoundingBox = YES; return self; }


// here's the deal.  Given a bounding box, we're going to transform that
// by the given transformation matrix.  Since this effects the extent
// of the object, we have to transform all 8 points of the bounding box.
// After transforming them, we the redetermine the min and max of things
- determineBound:(RtBound *)newBB givenBound:(RtBound *)aBB andCTM:(RtMatrix)aCTM 
{
   RtPoint  bboxPoints[8], bboxPointsTransformed[8];
   int      i;
            

   N3D_XComp(bboxPoints[0]) = (*aBB)[0];
   N3D_YComp(bboxPoints[0]) = (*aBB)[2];
   N3D_ZComp(bboxPoints[0]) = (*aBB)[4];

   N3D_XComp(bboxPoints[1]) = (*aBB)[1];
   N3D_YComp(bboxPoints[1]) = (*aBB)[2];
   N3D_ZComp(bboxPoints[1]) = (*aBB)[4];

   N3D_XComp(bboxPoints[2]) = (*aBB)[0];
   N3D_YComp(bboxPoints[2]) = (*aBB)[3];
   N3D_ZComp(bboxPoints[2]) = (*aBB)[4];

   N3D_XComp(bboxPoints[3]) = (*aBB)[1];
   N3D_YComp(bboxPoints[3]) = (*aBB)[3];
   N3D_ZComp(bboxPoints[3]) = (*aBB)[4];

   N3D_XComp(bboxPoints[4]) = (*aBB)[0];
   N3D_YComp(bboxPoints[4]) = (*aBB)[2];
   N3D_ZComp(bboxPoints[4]) = (*aBB)[5];

   N3D_XComp(bboxPoints[5]) = (*aBB)[1];
   N3D_YComp(bboxPoints[5]) = (*aBB)[2];
   N3D_ZComp(bboxPoints[5]) = (*aBB)[5];

   N3D_XComp(bboxPoints[6]) = (*aBB)[0];
   N3D_YComp(bboxPoints[6]) = (*aBB)[3];
   N3D_ZComp(bboxPoints[6]) = (*aBB)[5];

   N3D_XComp(bboxPoints[7]) = (*aBB)[1];
   N3D_YComp(bboxPoints[7]) = (*aBB)[3];
   N3D_ZComp(bboxPoints[7]) = (*aBB)[5];

   N3DMult3DPoints(bboxPoints, 8, aCTM, bboxPointsTransformed);

   // start off setting newBB to be the first point
   (*newBB)[0] = (*newBB)[1] = N3D_XComp(bboxPointsTransformed[0]);
   (*newBB)[2] = (*newBB)[3] = N3D_YComp(bboxPointsTransformed[0]);
   (*newBB)[4] = (*newBB)[5] = N3D_ZComp(bboxPointsTransformed[0]);

   for (i = 1; i < 8; i++)
   {  //// X
      if ((*newBB)[0] > N3D_XComp(bboxPointsTransformed[i]))
      {  (*newBB)[0] = N3D_XComp(bboxPointsTransformed[i]);
      }
      if ((*newBB)[1] < N3D_XComp(bboxPointsTransformed[i]))
      {  (*newBB)[1] = N3D_XComp(bboxPointsTransformed[i]);
      }
      //// Y
      if ((*newBB)[2] > N3D_YComp(bboxPointsTransformed[i]))
      {  (*newBB)[2] = N3D_YComp(bboxPointsTransformed[i]);
      }
      if ((*newBB)[3] < N3D_YComp(bboxPointsTransformed[i]))
      {  (*newBB)[3] = N3D_YComp(bboxPointsTransformed[i]);
      }
      //// Z
      if ((*newBB)[4] > N3D_ZComp(bboxPointsTransformed[i]))
      {  (*newBB)[4] = N3D_ZComp(bboxPointsTransformed[i]);
      }
      if ((*newBB)[5] < N3D_ZComp(bboxPointsTransformed[i]))
      {  (*newBB)[5] = N3D_ZComp(bboxPointsTransformed[i]);
      }
   }

   return self;
}


- (BOOL)pushesOrPopsCTM { return NO; }
- (BOOL)pushesCTM { return NO; }
- (BOOL)popsCTM { return NO; }

// the asumption here is that cmd (i - 1) pushes the CTM, so we want to 
// grovel over the remaining commands until we find one that pops the CTM
// it should recurse 
// the starting condition 
- (int)findBoundingBoxFromRIBCommands:(int)startingIndex using:attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  RtBound   tmpBoundingBox;
  RtMatrix  tmpCTM;
  int       i = startingIndex,
            incr,
            howMany = [ribCommandList count];
  id        cmd;
  id        newAttributeState;


  // first we need to find a bounding box to start with
  while (i < howMany)
  {  cmd = [ribCommandList objectAt:i];

     // if the cmd pops the attribute stack, we're done.
     // we return how many commands we've gone through in our time here
     if ([cmd popsCTM])
     {  i++;
        return (i - startingIndex);
     }

     if ([cmd pushesCTM])  // here we go again...
     {  newAttributeState = [[WW3DAttributeState alloc] init];
        i++;
        incr = [self findBoundingBoxFromRIBCommands:i using:newAttributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
        // we now need to merge this sub-attribute block into our current one
        if ([newAttributeState hasBoundingBox])
        {  // This means that the bounding box in newAttributeState needs to be
           // transformed by the ctm of attributeState, and then  we need to 
           // "grow" to the current bound with that tranformed sub-bound.

           [attributeState getTransformMatrix:tmpCTM];
          WW3DDetermineBoundGivenBoundAndCTM(&tmpBoundingBox, [newAttributeState boundingBox], tmpCTM);
          [attributeState growBoundingBox:&tmpBoundingBox];
       }
       else
       {  //NXLogError("warning: WW3DShape <%s> has a moot set of commands at indices %d to %d\n", [self shapeName], (i - 1), (i + incr - 1));
          //NXLogError("\tif there was a LightSource or AreaLight source in there, you can ignore this warning, though...\n");
       }
       i += incr;
     }
     else 
     {  [cmd transformCTM:attributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
        i++;
     }

    if ([cmd hasBoundingBox])
    {  // great! we grow the bound of the current attribute state
       [attributeState growBoundingBox:[cmd boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]];
    }

  }
  return (i - startingIndex);
}



- calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  RtBound   tmpBoundingBox;
  RtMatrix  childMatrix, tmpCTM;
  int       i, startingChildIndex = 0, howManyKids;
  id        child, kids, newAttributeState;


  //NXLogError("calculating bbox for shape <%s>\n", [self shapeName]);

  // this one is tricky.  we'll need to be smart about this...

  // basically, each RIBCommand has it's own bounding box.  We ask
  // each one in turn for it's boundingBox, and use that to build up ours.
  // Remember that each command is smart enough to know if any of it's
  // parameters have changed, but we need to be smart to realize if we have
  // rotated/translated/scaled.  Recall that for scaling and translating we
  // can just add that to the boundingBox without asking everyone to recalculate,
  // but rotating mucks up everything.  

  // so we need to transform these points against our 4x4, right?
  // Does the 3DKit take care of that for us?  Hmmm...

  // Actually, I don't think we need to cat our 4x4 against it, 
  // although I'm still unclear...  Anyway, what we really want to do is 
  // grovel over our ribCommandList and, to each object which answers YES
  // to hasBoundingBox we send the appropriate msg to get the bounding box
  // and compare it against ours.

  // actually, one more difficulty is that even though we might not
  // have any geometry (i.e. have any RIBCommands that respond 
  // positively to -hasBoundingBox), we still might have some commands
  // transform the boundingBox, ie. do more than concatenate an identity 
  // matrix against the matrix passed into -transform:.

  // what if you had 3 commands, the first two of which didn't have bounding boxes.
  // first time through, i == 1 at the end
  // next time, i == 2 at the end
  // final time through, i doesn't get incremented, but we are done
  // actually, it's more complicated than that.
  // some of these RIBCommands might not have a bounding box, but they do transform the CTM
  // also, you might have some transformational commands, then some geometry, then more
  // transformational commands, then geometry, then some more transformational... These
  // would all effect the children shapes in toto, but would also be affecting the 
  // bounding box of the various geometry commands.
  // urgh...

  // It's not really so bad.  Everything is taking place in the space of this shape, which 
  // means that this shape's transformation matrix isn't consulted.  We start off with 
  // the identity matrix, and transform a tmp CTM (current transformation matrix) as we
  // move through the commands.

  // Actually, it's a bit trickier than you might think.  The problem
  // is that although we have no fear of dropping down into a child until
  // we get to the list of kids, we just might hit a RIBAttributeBegin,
  // RIBTransformBegin, or RIBSolidBegin object, which would have the same effect
  // So, it looks like we need to extend the WWRenderable protocol to let us know
  // if the object pushes or pops the transformation stack.  We'll need to recurse
  // down the transformation tree, building up the CTM, and transforming any bounding
  // box we find.  We start off when the command pushes the stack.  We cons up a new
  // CTM and set it to the Identity matrix.  We then start walking down the commands,
  // first asking the command if it has a boundingBox.  If it does, we set our bound
  // to it, and then transform the points in the bound by our CTM.  We
  // then continue walking down the tree, asking each command if has a
  // bounding box, and if does, transforming that box by the CTM, and then
  // comparing it to our current bound, and growing it accordingly.  If the
  // command doesn't have a bound, we just transform our CTM and our
  // current bound by it.  We continue this until we get find a command
  // which pops the attribute stack.  If, along the way, we hit a command which
  // actually pushes the stack, we need to recurse down and call this routine again.
  // The routine should be passed, and return, the id of a list of WW3DAttributeState
  // objects, which have a bounding box, a CTM, etc. in them.

  newAttributeState = [[WW3DAttributeState alloc] init];
  i = [self findBoundingBoxFromRIBCommands:0 using:newAttributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
  if (i != [ribCommandList count])
  {  NXLogError("warning: shape <%s> has an unbalanced attribute delimiter at command %d (expected %d)\n", 
                [self shapeName], i, [ribCommandList count]);
     return nil;
  }

  [newAttributeState getTransformMatrix:tmpCTM];

  if (![newAttributeState hasBoundingBox])  // this Shape has no real geometry in it, but it might have kids...
  {  // we still need some approximation to start off with for this shape's bounding box.
     // to get it, we use our first child's bounding box.
     // if we don't have any kids, we just return.
     kids = [self children];
     if ([kids count] < 1)
     {  dirtyBoundingBox = NO;
        return self;
     }

     // note that it's fair to assume that if we have children, each one has a bounding
     // box.  If it didn't have a bounding box, that would mean it was a leaf node of
     // the tree without any geometry, which means we've got a moot node.
     child = [kids objectAt:0];

     // now, we can't just copy this bounding box, because it needs to
     // be transformed by the 4x4 that represents the transformation from this
     // shape to the child, i.e. it's 4x4.  
     N3D_CopyBound(*([child boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]), tmpBoundingBox);

     WW3DDetermineBoundGivenBoundAndCTM(&boundingBox, &tmpBoundingBox, tmpCTM);

     startingChildIndex = 1;
  }
  else
  {  N3D_CopyBound(*([newAttributeState boundingBox]), boundingBox);
  }

  // at this point, we only have a bounding box for the geometry
  // associated with this shape's geometry.  We now need to recurse down
  // our children, asking each to calculate it's bounding box.  We then
  // compare that bounding box with our own, and update ours accordingly.
  // note that if we didn't have any geometry in this shape, we've already
  // looked at the boundingBox of our first child, and set our boundingBox
  // to that, so startingChildIndex has been incremented to 1, so we
  // don't waste time redoing that.
  kids = [self children];
  howManyKids = [kids count];
  for (i = startingChildIndex; i < howManyKids; i++)
  {  child = [kids objectAt:i];
     N3D_CopyBound(*([child boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]), tmpBoundingBox);

     // the boundingBox that we get from the child should already be transformed 
     // correctly, right?  Or do we also need to concat the child's transform
     // onto it?  Yes, I think we do need to.  We get the bound, convert it to points,
     // transform it against the child's CTM, then multiply *that* by the
     // tmpCTM (which takes into account transformational RIBCommands in this
     // shape, which will get executed before the child shape, and then
     // convert those points back to a bound.  We can check that against 
     // the current bound.

     // we need to transform that bounding box to reflect the transformational 
     // commands that precede it in this shape's ribCommandList
     [child getTransformMatrix:childMatrix];
     WW3DDetermineBoundGivenBoundAndCTM(&tmpBoundingBox, &tmpBoundingBox, childMatrix);
     WW3DDetermineBoundGivenBoundAndCTM(&tmpBoundingBox, &tmpBoundingBox, tmpCTM);

     // now we should all be in the same space; compare against current boundingBox
     //// X
     if (tmpBoundingBox[0] < boundingBox[0])
     {  boundingBox[0] = tmpBoundingBox[0];
     }
     if (tmpBoundingBox[1] > boundingBox[1])
     {  boundingBox[1] = tmpBoundingBox[1];
     }
     //// Y
     if (tmpBoundingBox[2] < boundingBox[2])
     {  boundingBox[2] = tmpBoundingBox[2];
     }
     if (tmpBoundingBox[3] > boundingBox[3])
     {  boundingBox[3] = tmpBoundingBox[3];
     }
     //// Z
     if (tmpBoundingBox[4] < boundingBox[4])
     {  boundingBox[4] = tmpBoundingBox[4];
     }
     if (tmpBoundingBox[5] > boundingBox[5])
     {  boundingBox[5] = tmpBoundingBox[5];
     }
  }

  xExtent = boundingBox[1] - boundingBox[0];
  yExtent = boundingBox[3] - boundingBox[2];
  zExtent = boundingBox[5] - boundingBox[4];

  //fprintf(stderr, "shape %s has extents of (%f, %f, %f)\n", [self shapeName], xExtent, yExtent, zExtent);
  //fprintf(stderr, "\t bbox : X (%f, %f)\n", boundingBox[0], boundingBox[1]);
  //fprintf(stderr, "\t      : Y (%f, %f)\n", boundingBox[2], boundingBox[3]);
  //fprintf(stderr, "\t      : Z (%f, %f)\n", boundingBox[4], boundingBox[5]);

  dirtyBoundingBox = NO;

  return self;
}

#define X_AXIS 1.0, 0.0, 0.0
#define Y_AXIS 0.0, 1.0, 0.0
#define Z_AXIS 0.0, 0.0, 1.0
#define	wwMax(a,b)  (((a)>(b))?(a):(b))
#define	wwMin(a,b)  (((a)>(b))?(b):(a))

- drawOriginStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{  if (drawOrigin)
   {  [self boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime];  // call this to make sure that my boundingBox is current...
      RiAttributeBegin();
      RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, originTesselationVector, RI_NULL);

      // draw along Z axis
      RiColor(zColor);
      if (wwMin(xExtent, yExtent) == 0.0)
      {  if ((boundingBox[4] < 0.0) && (boundingBox[5] < 0.0))
         {  RiCylinder((0.01 * (wwMax(xExtent, yExtent))), 
		       boundingBox[4], 0.0, 
		       360.0, RI_NULL);
	 }
         else
         {  RiCylinder((0.01 * (wwMax(xExtent, yExtent))), 
		       wwMin(0.0, boundingBox[4]), boundingBox[5], 
		       360.0, RI_NULL);
	 }
      }
      else
      {  if ((boundingBox[4] < 0.0) && (boundingBox[5] < 0.0))
         {  RiCylinder((0.01 * (wwMin(xExtent, yExtent))), 
		       boundingBox[4], 0.0, 
		       360.0, RI_NULL);
 	 }
         else
         {  RiCylinder((0.01 * (wwMin(xExtent, yExtent))), 
		       wwMin(0.0, boundingBox[4]), boundingBox[5], 
		       360.0, RI_NULL);
 	 }
      }

      // rotate about X -90 degrees to be pointing along the Y axis
      RiRotate(-90, X_AXIS);
      RiColor(yColor);
      if (wwMin(xExtent, zExtent) == 0.0)
      {  if ((boundingBox[2] < 0.0) && (boundingBox[3] < 0.0)) 
         {  RiCylinder((0.01 * (wwMax(xExtent, zExtent))), 
		       boundingBox[2], 0.0,
		       360.0, RI_NULL);
	 }
         else
         {  RiCylinder((0.01 * (wwMax(xExtent, zExtent))), 
		       wwMin(0.0, boundingBox[2]), boundingBox[3], 
		       360.0, RI_NULL);
         }
      }
      else
      {  if ((boundingBox[2] < 0.0) && (boundingBox[3] < 0.0)) 
         {  RiCylinder((0.01 * (wwMin(xExtent, zExtent))), 
		       boundingBox[2], 0.0,
		       360.0, RI_NULL);
	 }
         else
	 {  RiCylinder((0.01 * (wwMin(xExtent, zExtent))), 
		       wwMin(0.0, boundingBox[2]), boundingBox[3], 
		       360.0, RI_NULL);
	 }
      }

      // rotate about Y 90 degrees to be pointing along the X axis
      RiRotate(90, Y_AXIS);
      RiColor(xColor);
      if (wwMin(yExtent, zExtent) == 0.0)
      {  if ((boundingBox[0] < 0.0) && (boundingBox[1] < 0.0))
         {   RiCylinder((0.01 * (wwMax(yExtent, zExtent))), 
			boundingBox[0], 0.0,
			360.0, RI_NULL);
	 }
	 else
         {   RiCylinder((0.01 * (wwMax(yExtent, zExtent))), 
			wwMin(0.0, boundingBox[0]), boundingBox[1], 
			360.0, RI_NULL);
	 }
      }
      else
      {  if ((boundingBox[0] < 0.0) && (boundingBox[1] < 0.0))
	 {   RiCylinder((0.01 * (wwMin(yExtent, zExtent))), 
			boundingBox[0], 0.0,
			360.0, RI_NULL);
	 }
         else
	 {   RiCylinder((0.01 * (wwMin(yExtent, zExtent))), 
			wwMin(0.0, boundingBox[0]), boundingBox[1], 
			360.0, RI_NULL);
	 }
      }
      RiAttributeEnd();
   }
   if (selected) 
   {  RiColor(selectedColor);
   }
   return self;
}


- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
{
  int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[ribCommandList objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  }
  [descendant renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  [nextPeer renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];

  return self;
}


- renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns
{
  int      i, howMany = [ribCommandList count];
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  for (i = 0; i < howMany; i++)
  {  [(id <WWAnimatable>)[ribCommandList objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  }
  [descendant renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  [nextPeer renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];

  return self;
}



- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[ribCommandList objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  [descendant renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  [nextPeer renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];

  return self;
}


- renderMaps:(WW3DCamera *)camera
{
  int      i, howMany = [ribCommandList count];
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  for (i = 0; i < howMany; i++)
  {  [(id <WWAnimatable>)[ribCommandList objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  [descendant renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  [nextPeer renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];

  return self;
}



- renderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int                i, howMany = [ribCommandList count];
  id <WWRenderable>  obj;


  if ([self doesDrawAsBox])
  {  return self;
  }

  // for the origin, we want cylinders the length of the bounding box
  // I could see wanting to do this only on-screen, but sometimes for real...
  if (NXDrawingStatus == NX_DRAWING) 
  {  [self drawOriginStartingAt:shutterOpenTime endingAt:shutterCloseTime];
     for (i = 0; i < howMany; i++)
     {  [(id <WWRenderable>)[ribCommandList objectAt:i] renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }
  else
  {  for (i = 0; i < howMany; i++) // do the moot check
     {  obj = (id <WWRenderable>)[ribCommandList objectAt:i];
        if (![obj isMoot])
        {  [obj renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
        }
     }
  }
  return self;
}


- renderSelf:(WW3DCamera *)camera
{
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}

// I don't like the way NeXT defines this; should recurse down
- setDrawAsBox:(BOOL)flag
{
  int  i, howMany;
  id   kids;


  [super setDrawAsBox:flag];
  kids = [self children];
  howMany = [kids count];
  for (i = 0; i < howMany; i++)
  {  [[kids objectAt:i] setDrawAsBox:flag];
  }

  return self;
}

- renderSelfAsBox:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int      i, howMany = [ribCommandList count];
  RtInt    nPolys = 6;
  RtInt    nVerts[6] = {4, 4, 4, 4, 4, 4 };
  RtInt    verts[24] = {3, 2, 6, 7, 
                        2, 1, 5, 6,
                        1, 0, 4, 5,
                        0, 3, 7, 4,
                        7, 6, 5, 4,
                        0, 1, 2, 3};
  RtPoint  thePointsOfACube[8];
  RtBound  bb;


  if (NXDrawingStatus == NX_DRAWING) 
  {  [self drawOriginStartingAt:shutterOpenTime endingAt:shutterCloseTime];
  }

  N3D_CopyBound(*([self boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]), bb);
  thePointsOfACube[0][0] = bb[0];
  thePointsOfACube[0][1] = bb[3];
  thePointsOfACube[0][2] = bb[5];

  thePointsOfACube[1][0] = bb[1];
  thePointsOfACube[1][1] = bb[3];
  thePointsOfACube[1][2] = bb[5];

  thePointsOfACube[2][0] = bb[1];
  thePointsOfACube[2][1] = bb[3];
  thePointsOfACube[2][2] = bb[4];

  thePointsOfACube[3][0] = bb[0];
  thePointsOfACube[3][1] = bb[3];
  thePointsOfACube[3][2] = bb[4];

  thePointsOfACube[4][0] = bb[0];
  thePointsOfACube[4][1] = bb[2];
  thePointsOfACube[4][2] = bb[5];

  thePointsOfACube[5][0] = bb[1];
  thePointsOfACube[5][1] = bb[2];
  thePointsOfACube[5][2] = bb[5];

  thePointsOfACube[6][0] = bb[1];
  thePointsOfACube[6][1] = bb[2];
  thePointsOfACube[6][2] = bb[4];

  thePointsOfACube[7][0] = bb[0];
  thePointsOfACube[7][1] = bb[2];
  thePointsOfACube[7][2] = bb[4];

  RiPointsPolygons(nPolys, nVerts, verts, RI_P, thePointsOfACube, RI_NULL);

  // we now need to render our rib commands.
  // we need to do this so that our children shapes are transformed correctly
  // this is actually a bit weird, because the rib commands 

  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[ribCommandList objectAt:i] renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }

  return self;
}

- renderSelfAsBox:(N3DCamera *)camera
{
  RtFloat  shutterOpenTime = [(WW3DCamera *)camera shutterOpenTime],
           shutterCloseTime = [(WW3DCamera *)camera shutterCloseTime];


  return [self renderSelfAsBox:(WW3DCamera *)camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}


- preRenderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[ribCommandList objectAt:i] preRenderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  return self;
}


- preRenderSelf:(WW3DCamera *)camera
{
  int  i, howMany = [ribCommandList count];
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  for (i = 0; i < howMany; i++)
  {  [(id <WWAnimatable>)[ribCommandList objectAt:i] preRenderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  return self;
}


- setShadingGroup:(const char *)newGroup
{
  if (shadingGroup) { free(shadingGroup); }
  shadingGroup = NXCopyStringBuffer(newGroup);
  return self;
}

- setMaterialGroup:(const char *)newGroup
{
  if (materialGroup) { free(materialGroup); }
  materialGroup = NXCopyStringBuffer(newGroup);
  return self;
}

- setGeometryGroup:(const char *)newGroup
{
  if (geometryGroup) { free(geometryGroup); }
  geometryGroup = NXCopyStringBuffer(newGroup);
  return self;
}


- ribCommands { return ribCommandList; }
- appendRIBCommand:newRIBCommand
{
    if ([newRIBCommand isKindOf:[EveCommand class]]) {
	hasEveCmd = YES;
    }
    [ribCommandList addObject:newRIBCommand];
    dirtyBoundingBox = YES;
    return self;
}

- setSelected:(BOOL)selectionFlag andDrawOrigin:(BOOL)drawOriginFlag 
{  selected = selectionFlag; 
   [self setDrawOrigin:drawOriginFlag];
   return self; 
}

- setDrawOrigin:(BOOL)drawOriginFlag  {  drawOrigin = drawOriginFlag; return self; }

- updateBases
{
  int   howManyPositions, i;
  BOOL  foundIt;
  id    cmd = nil;   


  howManyPositions = [ribCommandList count];
  i = howManyPositions - 1;
  foundIt = NO;
  while (!foundIt && (i > 0))
  {  cmd = [ribCommandList objectAt:i];
     if ([cmd respondsTo:@selector(uBasis)])
     {  foundIt = YES;
     }
     else
     {  i--;
     }
  }

  if (foundIt) // that's it; copy it from the last RIBBasis cmd
  {  N3D_CopyMatrix(*([cmd uBasis]), uBasis);
     uBasisToken = [ancestor uBasisToken];
     uStep = [cmd uStep];
     N3D_CopyMatrix(*([cmd vBasis]), vBasis);
     vBasisToken = [ancestor vBasisToken];
     vStep = [cmd vStep];
  }
  else  // not defined here; ask up the shape hierarchy
  {  if ((ancestor) && (ancestor != self)) // get it from our ancestor
     {  N3D_CopyMatrix(*([ancestor uBasis]), uBasis);
        uBasisToken = [ancestor uBasisToken];
        uStep = [ancestor uStep];
        N3D_CopyMatrix(*([ancestor vBasis]), vBasis);
        vBasisToken = [ancestor vBasisToken];
        vStep = [ancestor vStep];
     }
     else // we are the root; fill in bezier bases for both u and v
     {  N3D_CopyMatrix(RiBezierBasis, uBasis);
        uBasisToken = RI_BEZIER;
        uStep = 3;
        N3D_CopyMatrix(RiBezierBasis, vBasis);
        vStep = 3;
        vBasisToken = RI_BEZIER;
     }
  }

  dirtyBases = NO;
  return self;
}


- (RtBound *)boundingBoxStartingAt:(RtFloat)intervalStart endingAt:(RtFloat)intervalEnd
{ 
   if (!dirtyBoundingBox)
   {  // okay, this means we haven't been sent any messages that would have invalidated the boundingBox cache
      // now check intervals...
      if ((intervalStart == bbIntervalStart) && (intervalEnd == bbIntervalEnd))
      {  // cool! we can use the cached version!!
         return &boundingBox; 
      }
   }
   // damn! have to recalculate it..
   [self calculateBoundingBoxStartingAt:intervalStart endingAt:intervalEnd];
   // don't forget to refill the cache
   bbIntervalStart = intervalStart;
   bbIntervalEnd = intervalEnd;
   dirtyBoundingBox = FALSE;

   // so what things might invalidate the cache?  Basically if I get a
   // new child or an addition to my ribCommandList, the bounding box
   // is invalidated.

   return &boundingBox; 
}

- (RtBasis *)uBasis {  if (dirtyBases) {  [self updateBases]; }   return &uBasis; }
- (RtToken)uBasisToken {  if (dirtyBases) {  [self updateBases]; }  return uBasisToken; }
- (RtInt)uStep {  if (dirtyBases) {  [self updateBases]; }  return uStep; }
- (RtBasis *)vBasis {  if (dirtyBases) {  [self updateBases]; }  return &vBasis; }
- (RtToken)vBasisToken {  if (dirtyBases) {  [self updateBases]; }  return vBasisToken; }
- (RtInt)vStep {  if (dirtyBases) {  [self updateBases]; }   return vStep; }

- setTransformMatrix:(RtMatrix)tm
{
   //[self logTransformWithMsg:"setTransformMatrix:(RtMatrix)tm BEFORE"];
   [super setTransformMatrix:tm];
   //[self logTransformWithMsg:"setTransformMatrix:(RtMatrix)tm AFTER"];
   return self;
}

- concatTransformMatrix:(RtMatrix)ctm premultiply:(BOOL)flag
{
   //[self logTransformWithMsg:"concatTransformMatrix:(RtMatrix)ctm premultiply:(BOOL)flag BEFORE"];
   [super concatTransformMatrix:ctm premultiply:flag];
   //[self logTransformWithMsg:"concatTransformMatrix:(RtMatrix)ctm premultiply:(BOOL)flag AFTER"];
   return self;
}

- rotateAngle:(float)ang axis:(RtPoint)anAxis
{
   //[self logTransformWithMsg:"rotateAngle:(float)ang axis:(RtPoint)anAxis BEFORE"];
   [super rotateAngle:ang axis:anAxis];
   //[self logTransformWithMsg:"rotateAngle:(float)ang axis:(RtPoint)anAxis AFTER"];
   return self;
}

- preRotateAngle:(float)ang axis:(RtPoint)anAxis
{
   //[self logTransformWithMsg:"preRotateAngle:(float)ang axis:(RtPoint)anAxis BEFORE"];
   [super preRotateAngle:ang axis:anAxis];
   //[self logTransformWithMsg:"preRotateAngle:(float)ang axis:(RtPoint)anAxis AFTER"];
   return self;
}

- scale:(float)sx :(float)sy :(float)sz
{
   //[self logTransformWithMsg:"scale:(float)sx :(float)sy :(float)sz BEFORE"];
   [super scale:sx :sy :sz];
   //[self logTransformWithMsg:"scale:(float)sx :(float)sy :(float)sz AFTER"];
   return self;
}

- preScale:(float)sx :(float)sy :(float)sz
{
   //[self logTransformWithMsg:"preScale:(float)sx :(float)sy :(float)sz BEFORE"];
   [super preScale:sx :sy :sz];
   //[self logTransformWithMsg:"preScale:(float)sx :(float)sy :(float)sz AFTER"];
   return self;
}

- scaleUniformly:(float)s
{
   //[self logTransformWithMsg:"scaleUniformly:(float)s BEFORE"];
   [super scaleUniformly:s];
   //[self logTransformWithMsg:"scaleUniformly:(float)s AFTER"];
   return self;
}

- preScaleUniformly:(float)s
{
   //[self logTransformWithMsg:"preScaleUniformly:(float)s BEFORE"];
   [super preScaleUniformly:s];
   //[self logTransformWithMsg:"preScaleUniformly:(float)s AFTER"];
   return self;
}

- translate:(float)tx :(float)ty :(float)tz
{
   //[self logTransformWithMsg:"translate:(float)tx :(float)ty :(float)tz BEFORE"];
   [super translate:tx :ty :tz];
   //[self logTransformWithMsg:"translate:(float)tx :(float)ty :(float)tz AFTER"];
   return self;
}

- preTranslate:(float)tx :(float)ty :(float)tz
{
   //[self logTransformWithMsg:"preTranslate:(float)tx :(float)ty :(float)tz BEFORE"];
   [super preTranslate:tx :ty :tz];
   //[self logTransformWithMsg:"preTranslate:(float)tx :(float)ty :(float)tz AFTER"];
   return self;
}


- logTransformWithMsg:(const char *)aMsg
{
   if (aMsg)
   {  NXLogError("WW3DShape %s transform matrix: %s\n", [self shapeName], aMsg);
   }
   else
   {  NXLogError("WW3DShape %s transform matrix\n", [self shapeName]);
   }
   NXLogError("\t%f\t%f\t%f\t%f\n", transform[0][0], transform[0][1], transform[0][2], transform[0][3]); 
   NXLogError("\t%f\t%f\t%f\t%f\n", transform[1][0], transform[1][1], transform[1][2], transform[1][3]); 
   NXLogError("\t%f\t%f\t%f\t%f\n", transform[2][0], transform[2][1], transform[2][2], transform[2][3]); 
   NXLogError("\t%f\t%f\t%f\t%f\n", transform[3][0], transform[3][1], transform[3][2], transform[3][3]); 
   return self;
}

- addChild:newChild
{
  id  oldDescendant = nil,
      oldDescendantList = [[List alloc] init],
      parentShape = self;


  // I think I want to unlink the old descendant, link the child in as
  // the new one, and then make the oldDescendant the peer of the child.
  oldDescendant = [parentShape descendant];
  if (oldDescendant)
  {  while (oldDescendant) 
     {  [oldDescendantList addObject:oldDescendant];
        [oldDescendant unlink];
        oldDescendant = [parentShape descendant];
     }
  }
  [parentShape linkDescendant:newChild];
  if (oldDescendantList)
  {  oldDescendant = [oldDescendantList objectAt:0];
     while (oldDescendant) 
     {  [newChild linkPeer:oldDescendant];
        [oldDescendantList removeObjectAt:0];
        oldDescendant = [oldDescendantList objectAt:0];
     }
     [oldDescendantList free];
  }
  dirtyBoundingBox = YES;

  return self;
}


- siblings
{ 
  id  currentPeer;


  // Really should cache this by trapping all the hierarchy frobbing and
  // having a dirty bit set.  For right now, recalculate each time.

  [siblingList empty];

  if (self != [self firstPeer])
  {  [siblingList addObject:[self firstPeer]];
     currentPeer = [self firstPeer];

     while ([currentPeer nextPeer])
     {  [siblingList addObject:[currentPeer nextPeer]];
        currentPeer = [currentPeer nextPeer];
     }
  }

  return siblingList;
}

- children 
{ 
  id  currentPeer;


  // Really should cache this by trapping all the hierarchy frobbing and
  // having a dirty bit set.  For right now, recalculate each time.

  [childList empty];

  [childList addObject:[self descendant]];
  currentPeer = descendant;
  // note that the descendant is the firstPeer of it's siblings...
  while ([currentPeer nextPeer])
  {  [childList addObject:[currentPeer nextPeer]];
     currentPeer = [currentPeer nextPeer];
  }

  return childList;
}

- parent { return ancestor; }

- (unsigned short)pathSeparator { return pathSeparator; }
- setPathSeparator:(unsigned short)newSeparator { pathSeparator = newSeparator; return self; }


- getChildGivenPath:(const char *)path
{
  char  *part = (char *)path;
  int   cnt, howMany, i;
  id    ret;


  // the string looks something like "/foo/bar/zap/zow"
  // if the whole string is "/", return yourself
  // if the first part doesn't match your name, return nil.
  // if what's left after the first part is nil, return self;
  // if you're still here, send the message to each child until it doesn't return nil.  
  // if you get through all your children and it still returns nil, return nil.

  if ((strlen(path) == 1) && ((unsigned short)(*part) == pathSeparator))
  {  return self;
  }

  while (*part && ((unsigned short)(*part) != pathSeparator)) { part++; } 
  // from "/foo/bar" to "/bar" or from "/foo" to ""
  cnt = part - (path + 1);
  if (!cnt) {  return nil; } // catch the "/foo" to "" case

  if (!strncmp((path+1), [self shapeName], cnt))
  {  // the first part is my name
     if (!*part) { return self; }// if there isn't anything else, I'm who they're looking for

     // otherwise, send message to each child to see if it's them
     howMany = [childList count];
     i = 0;
     while (i < howMany)
     {  ret = [[childList objectAt:i] getChildGivenPath:part];
        if (ret)
        {  return ret;
        }
        i++;
     }
  }
  // that isn't my name at the beginning - this isn't for me or any of my kids
  return nil;
}

- (char *)getPath
{
  char  *path, pathSeparatorString[2]; 
  int   cnt = 1, 
        i;
  id    theShape;
  List  *myShapeList;


  myShapeList = [[List alloc] init];
  theShape = self;
  while (theShape)
  {  cnt += 2 + strlen([theShape shapeName]);
     [myShapeList addObject:theShape];
     theShape = [theShape ancestor];
  }

  path = malloc(cnt);
  if (!path)
  {  [myShapeList free];
     return NULL;
  }

  path[0] = '\0';
  sprintf(pathSeparatorString, "%c", pathSeparator);
  for (i = [myShapeList count] - 1; i >= 0; i--)  
  {  strcat(path, pathSeparatorString);
     strcat(path, [[myShapeList objectAt:i] shapeName]);
  }
  
  return path;
}

- getInitialTransformMatrix:(RtMatrix)aMatrix
{
  N3D_CopyMatrix(N3DIdentityMatrix, aMatrix);
  return self;
}

- setSelectedColor:(RtColor)newColor
{  selectedColor[0] = newColor[0];
   selectedColor[1] = newColor[1];
   selectedColor[2] = newColor[2];

   return self;
}
- (RtColor *)selectedColor { return &selectedColor; }

- setUnselectedColor:(RtColor)newColor
{  unselectedColor[0] = newColor[0];
   unselectedColor[1] = newColor[1];
   unselectedColor[2] = newColor[2];

   return self;
}
- (RtColor *)unselectedColor { return &unselectedColor; }


// WavesWorld archiving:
// writeEve:(NXStream *)stream
// writeScene:(NXStream *)stream

- (BOOL)matricesAreEqual:(RtMatrix)m1 :(RtMatrix)m2
{
  if (m1[0][0] != m2[0][0])  {  return NO; }
  if (m1[0][1] != m2[0][1])  {  return NO; }
  if (m1[0][2] != m2[0][2])  {  return NO; }
  if (m1[0][3] != m2[0][3])  {  return NO; }

  if (m1[1][0] != m2[1][0])  {  return NO; }
  if (m1[1][1] != m2[1][1])  {  return NO; }
  if (m1[1][2] != m2[1][2])  {  return NO; }
  if (m1[1][3] != m2[1][3])  {  return NO; }

  if (m1[2][0] != m2[2][0])  {  return NO; }
  if (m1[2][1] != m2[2][1])  {  return NO; }
  if (m1[2][2] != m2[2][2])  {  return NO; }
  if (m1[2][3] != m2[2][3])  {  return NO; }

  if (m1[3][0] != m2[3][0])  {  return NO; }
  if (m1[3][1] != m2[3][1])  {  return NO; }
  if (m1[3][2] != m2[3][2])  {  return NO; }
  if (m1[3][3] != m2[3][3])  {  return NO; }

  return YES;
}

- (BOOL)isIdentityMatrix:(RtMatrix)m  { return [self matricesAreEqual:(RtFloat (*)[4])N3DIdentityMatrix :m]; }

- writeEve:(NXStream *)stream atTabLevel:(int)tab
{
   RtMatrix  aMatrix;
   int       i, howManySpaces, howMany = [ribCommandList count];
   id        aCmd;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "startShape {%s} ", [self shapeName]); 
   [self getTransformMatrix:aMatrix];
   if ([self isIdentityMatrix:aMatrix])
   {  NXPrintf(stream, ";\n");
   }
   else  // need to add the transformation matrix, since it's not the identity matrix...
   {  NXPrintf(stream, "{");
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[0][0], aMatrix[0][1], aMatrix[0][2], aMatrix[0][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
      // it would be nice if we put these on separate lines, but line up...
      // how far in do we need to go? well, first we need to tab the right amount, and then
      // move over "startShape " + strlen([self shapeName]) + "{"....
      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      howManySpaces = strlen("startShape ") + strlen([self shapeName]) + strlen(" {");

      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[1][0], aMatrix[1][1], aMatrix[1][2], aMatrix[1][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
   
      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[2][0], aMatrix[2][1], aMatrix[2][2], aMatrix[2][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...

      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f};\n", aMatrix[3][0], aMatrix[3][1], aMatrix[3][2], aMatrix[3][3]);
      NXPrintf(stream, "\n");
   }
   // WAVE!!! Need to write out the rest of my shaders here!!!
   if (surfaceShader)
   {  [surfaceShader writeEve:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }
   if (displacementShader)
   {  [displacementShader writeEve:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   // okay, now tell all my rib commands to write themselves out...
   for (i = 0; i < howMany; i++)
   {  aCmd = [ribCommandList objectAt:i];
      [aCmd writeEve:stream atTabLevel:(tab+1)];
      NXPrintf(stream, "\n");
   }

   // now tell my descendant to writeEve: itself...
   if (descendant) // don't put an extra new-line out if I have no descendant...
   {  [descendant writeEve:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "endShape\n"); 

   // now tell my nextPeer to writeEve: itself...
   [nextPeer writeEve:stream atTabLevel:tab];

   return self;
}

- writeScene:(NXStream *)stream atTabLevel:(int)tab
{
   RtMatrix  aMatrix;
   int       i, howManySpaces, howMany = [ribCommandList count];
   id        aCmd;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "startShape {%s} ", [self shapeName]); 
   [self getTransformMatrix:aMatrix];
   if ([self isIdentityMatrix:aMatrix])
   {  NXPrintf(stream, ";\n");
   }
   else  // need to add the transformation matrix, since it's not the identity matrix...
   {  NXPrintf(stream, "{");
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[0][0], aMatrix[0][1], aMatrix[0][2], aMatrix[0][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
      // it would be nice if we put these on separate lines, but line up...
      // how far in do we need to go? well, first we need to tab the right amount, and then
      // move over "startShape " + strlen([self shapeName]) + "{"....
      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      howManySpaces = strlen("startShape ") + strlen([self shapeName]) + strlen(" {");

      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[1][0], aMatrix[1][1], aMatrix[1][2], aMatrix[1][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
   
      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[2][0], aMatrix[2][1], aMatrix[2][2], aMatrix[2][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...

      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f};\n", aMatrix[3][0], aMatrix[3][1], aMatrix[3][2], aMatrix[3][3]);
      NXPrintf(stream, "\n");
   }
   // WAVE!!! Need to write out the rest of my shaders here!!!
   if (surfaceShader)
   {  [surfaceShader writeScene:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }
   if (displacementShader)
   {  [displacementShader writeScene:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   // okay, now tell all my rib commands to write themselves out...
   for (i = 0; i < howMany; i++)
   {  aCmd = [ribCommandList objectAt:i];
      [aCmd writeScene:stream atTabLevel:(tab+1)];
      NXPrintf(stream, "\n");
   }

   // now tell my descendant to writeScene: itself...
   if (descendant) // don't put an extra new-line out if I have no descendant...
   {  [descendant writeScene:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "endShape\n"); 

   // now tell my nextPeer to writeScene: itself...
   [nextPeer writeScene:stream atTabLevel:tab];

   return self;
}


- write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime
{
   RtMatrix  aMatrix;
   int       i, 
             howManySpaces, howMany = [ribCommandList count];
   id        aCmd;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }

   NXPrintf(stream, "set __text__(colorWW3DShape) {1 1 1}\n");

   NXPrintf(stream, "startShape WW3DShape; EveCmd {Color $__text__(colorWW3DShape)}; ");
   // need tab
   // need index (position in current list)
   NXPrintf(stream, 
	    "EveCmd {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", 
            tab, index); 
   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "  EveCmd {WW3DText $__text__(fontName) $__text__(fontSize) {");
     NXPrintf(stream, "startShape %s ", [self shapeName]); 
     [self getTransformMatrix:aMatrix];
     if ([self isIdentityMatrix:aMatrix])
     {  NXPrintf(stream, " } left;\n");
     }
     else  // need to add the transformation matrix, since it's not the identity matrix...
     {  NXPrintf(stream, "{");
        NXPrintf(stream, "%f %f %f %f ",  aMatrix[0][0], aMatrix[0][1], aMatrix[0][2], aMatrix[0][3]);
        NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
        // it would be nice if we put these on separate lines, but line up...
        // how far in do we need to go? well, first we need to tab the right amount, and then
        // move over "startShape " + strlen([self shapeName]) + "{"....
        for (i = 0; i < tab; i++)
        {  NXPrintf(stream, "\t");
        }
        howManySpaces = strlen("startShape ") + strlen([self shapeName]) + strlen(" {");
  
        for (i = 0; i < howManySpaces; i++)
        {  NXPrintf(stream, " ");
        }
        NXPrintf(stream, "%f %f %f %f ",  aMatrix[1][0], aMatrix[1][1], aMatrix[1][2], aMatrix[1][3]);
        NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...

        for (i = 0; i < tab; i++)
        {  NXPrintf(stream, "\t");
        }
        for (i = 0; i < howManySpaces; i++)
        {  NXPrintf(stream, " ");
        }
        NXPrintf(stream, "%f %f %f %f ",  aMatrix[2][0], aMatrix[2][1], aMatrix[2][2], aMatrix[2][3]);
        NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
  
        for (i = 0; i < tab; i++)
        {  NXPrintf(stream, "\t");
        }
        for (i = 0; i < howManySpaces; i++)
        {  NXPrintf(stream, " ");
        }
        NXPrintf(stream, "%f %f %f %f}} left;\n", aMatrix[3][0], aMatrix[3][1], aMatrix[3][2], aMatrix[3][3]);
        NXPrintf(stream, "\n");
     }
   NXPrintf(stream, "}\n");
  
   index++;
   // okay, next object - it will be a child of this shape:
   if (surfaceShader)
   {  [surfaceShader write3DTextScene:stream atTabLevel:(tab+1) index:index++ time:time until:lastTime];
      NXPrintf(stream, "\n");
   }

   // okay, now tell all my rib commands to write themselves out...
   for (i = 0; i < howMany; i++)
   {  aCmd = [ribCommandList objectAt:i];
      [aCmd write3DTextScene:stream atTabLevel:(tab+1) index:index++ time:time until:lastTime];
      NXPrintf(stream, "\n");
   }

   // now tell my descendant to writeEve: itself...
   if (descendant) // don't put an extra new-line out if I have no descendant...
   {  [descendant write3DTextScene:stream atTabLevel:(tab+1) index:index++ time:time until:lastTime];
      NXPrintf(stream, "\n");
   }

   // okay, now finish this text shape off by putting out the endShape

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "startShape endShape; ");
   // need tab
   // need index (position in current list)
   NXPrintf(stream, 
	    "EveCmd {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", 
            tab, index); 
   NXPrintf(stream, "  EveCmd {WW3DText $__text__(fontName) $__text__(fontSize) {");
     NXPrintf(stream, "endShape");
   NXPrintf(stream, "} left;}\n");

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
  NXPrintf(stream, "endShape;\n");

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
  NXPrintf(stream, "endShape;\n");  // okay, end off the WW3DShape

   // now tell my nextPeer to writeEve: itself...
   [nextPeer write3DTextScene:stream atTabLevel:(tab+1) index:index time:time until:lastTime];

   return self;
}


- writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab
{
   RtMatrix  aMatrix;
   int       i, newTab, howMany = [ribCommandList count];
   id        aCmd;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "# startShape {%s}\n", [self shapeName]);
   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "DEF \"%s\" Separator {\n", [self shapeName]);
   [self getTransformMatrix:aMatrix];
   if (![self isIdentityMatrix:aMatrix])
   {  

      for (i = 0; i < (tab+1); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "MatrixTransform {\n");
      for (i = 0; i < (tab+2); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "matrix %f %f %f %f\n", aMatrix[0][0], aMatrix[0][1], aMatrix[0][2], aMatrix[0][3]);
      for (i = 0; i < (tab+2); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "       %f %f %f %f\n",  aMatrix[1][0], aMatrix[1][1], aMatrix[1][2], aMatrix[1][3]);
      for (i = 0; i < (tab+2); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "       %f %f %f %f\n",  aMatrix[2][0], aMatrix[2][1], aMatrix[2][2], aMatrix[2][3]);
      for (i = 0; i < (tab+2); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "       %f %f %f %f\n", aMatrix[3][0], aMatrix[3][1], aMatrix[3][2], aMatrix[3][3]);
      for (i = 0; i < (tab+1); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "}\n");
   }
   // WAVE!!! Need to write out the rest of my shaders here!!!
   if (surfaceShader)
   {  [surfaceShader writeInventorAtTime:currentTime to:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }
   if (displacementShader)
   {  [displacementShader writeInventorAtTime:currentTime to:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   // okay, now tell all my rib commands to write themselves out...
   newTab = tab + 1;
   for (i = 0; i < howMany; i++)
   {  aCmd = [ribCommandList objectAt:i];
      if (![aCmd isMootStartingAt:currentTime endingAt:currentTime])
      {  if ([aCmd popsCTM])
         {  newTab--;
         }
         [aCmd writeInventorAtTime:currentTime to:stream atTabLevel:newTab];
         if ([aCmd pushesCTM])
         {  newTab++;
         }
         NXPrintf(stream, "\n");
      }
   }

   // now tell my descendant to writeInventor: itself...
   if (descendant) // don't put an extra new-line out if I have no descendant...
   {  [descendant writeInventorAtTime:currentTime to:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "}\n"); 

   // now tell my nextPeer to writeInventor: itself...
   [nextPeer writeInventorAtTime:currentTime to:stream atTabLevel:tab];

   return self;
}


// NeXTSTEP archiving:
#define typeVectorVersion1 "@S***[3f][3f][3f]f[3f]f[3f]f@"
#define typeValuesVersion1 &ribCommandList, &pathSeparator, \
                   &shadingGroup, &materialGroup, &geometryGroup, \
                   &selectedColor, &unselectedColor, \
                   &xColor, &xExtent, \
                   &yColor, &yExtent, \
                   &zColor, &zExtent, \
                   &interp

#define typeVector "@S***fff@@"
#define typeValues &ribCommandList, &pathSeparator, \
                   &shadingGroup, &materialGroup, &geometryGroup, \
                   &xExtent, &yExtent, &zExtent, \
                   &interp, &sceneClock


- read:(NXTypedStream *)stream 
{
    int    version;
    
    [super read:stream];
    
    NX_DURING
	version = NXTypedStreamClassVersion(stream, "WW3DShape");
	if (version == 0) NXReadTypes(stream, "i", &version), version = 1;
	if (version == 1) {
	    NXReadTypes(stream, typeVectorVersion1, typeValuesVersion1);
	    sceneClock = nil;
	    NXLogError("yikes! no sceneClock was archived with this shape...");
	}
	if (version == 2) {
	    NXReadTypes(stream, typeVector, typeValues);
	    NXReadArray(stream, "f", 3, selectedColor);
	    NXReadArray(stream, "f", 3, unselectedColor);
	    NXReadArray(stream, "f", 3, xColor);
	    NXReadArray(stream, "f", 3, yColor);
	    NXReadArray(stream, "f", 3, zColor);
	}
    NX_HANDLER
	NXLogError("in read: %s, exception [%d] raised.\n", [[self class] name], NXLocalHandler.code);
	return nil;
    NX_ENDHANDLER
    
    return self;
}


- write:(NXTypedStream *)stream 
{
    [super write:stream];
    NXWriteTypes(stream, typeVector, typeValues);
    NXWriteArray(stream, "f", 3, selectedColor);
    NXWriteArray(stream, "f", 3, unselectedColor);
    NXWriteArray(stream, "f", 3, xColor);
    NXWriteArray(stream, "f", 3, yColor);
    NXWriteArray(stream, "f", 3, zColor);
    return self;
}

// boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details
- class { return [super class]; }

@end
